/*
 * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
/*
 * Licensed Materials - Property of IBM
 * RMI-IIOP v1.0
 * Copyright IBM Corp. 1998 2012  All Rights Reserved
 *
 */

package com.sun.corba.se.impl.io;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.DigestOutputStream;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedAction;

import java.lang.reflect.Modifier;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationTargetException;

import java.io.IOException;
import java.io.DataOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.InvalidClassException;
import java.io.Externalizable;
import java.io.Serializable;

import java.util.Arrays;
import java.util.Comparator;

import com.sun.corba.se.impl.util.RepositoryId;

import org.omg.CORBA.ValueMember;

import sun.corba.Bridge;

/**
 * A ObjectStreamClass describes a class that can be serialized to a stream
 * or a class that was serialized to a stream.  It contains the name
 * and the serialVersionUID of the class.
 * <br>
 * The ObjectStreamClass for a specific class loaded in this Java VM can
 * be found using the lookup method.
 *
 * @author Roger Riggs
 * @since JDK1.1
 */
public class ObjectStreamClass implements java.io.Serializable {

  private static final boolean DEBUG_SVUID = false;

  public static final long kDefaultUID = -1;

  private static Object noArgsList[] = {};
  private static Class<?> noTypesList[] = {};

  /**
   * true if represents enum type
   */
  private boolean isEnum;

  private static final Bridge bridge =
      AccessController.doPrivileged(
          new PrivilegedAction<Bridge>() {
            public Bridge run() {
              return Bridge.get();
            }
          }
      );

  /**
   * Find the descriptor for a class that can be serialized.  Null
   * is returned if the specified class does not implement
   * java.io.Serializable or java.io.Externalizable.
   */
  static final ObjectStreamClass lookup(Class<?> cl) {
    ObjectStreamClass desc = lookupInternal(cl);
    if (desc.isSerializable() || desc.isExternalizable()) {
      return desc;
    }
    return null;
  }

  /*
   * Find the class descriptor for the specified class.
   * Package access only so it can be called from ObjectIn/OutStream.
   */
  static ObjectStreamClass lookupInternal(Class<?> cl) {
        /* Synchronize on the hashtable so no two threads will do
         * this at the same time.
         */
    ObjectStreamClass desc = null;
    synchronized (descriptorFor) {
            /* Find the matching descriptor if it already known */
      desc = findDescriptorFor(cl);
      if (desc == null) {
                /* Check if it's serializable */
        boolean serializable = Serializable.class.isAssignableFrom(cl);

                /* If the class is only Serializable,
                 * lookup the descriptor for the superclass.
                 */
        ObjectStreamClass superdesc = null;
        if (serializable) {
          Class<?> superclass = cl.getSuperclass();
          if (superclass != null) {
            superdesc = lookup(superclass);
          }
        }

                /* Check if its' externalizable.
                 * If it's Externalizable, clear the serializable flag.
                 * Only one or the other may be set in the protocol.
                 */
        boolean externalizable = false;
        if (serializable) {
          externalizable =
              ((superdesc != null) && superdesc.isExternalizable()) ||
                  Externalizable.class.isAssignableFrom(cl);
          if (externalizable) {
            serializable = false;
          }
        }

                /* Create a new version descriptor,
                 * it put itself in the known table.
                 */
        desc = new ObjectStreamClass(cl, superdesc,
            serializable, externalizable);
      }
      // Must always call init.  See bug 4488137.  This code was
      // incorrectly changed to return immediately on a non-null
      // cache result.  That allowed threads to gain access to
      // unintialized instances.
      //
      // History: Note, the following init() call was originally within
      // the synchronization block, as it currently is now. Later, the
      // init() call was moved outside the synchronization block, and
      // the init() method used a private member variable lock, to
      // avoid performance problems. See bug 4165204. But that lead to
      // a deadlock situation, see bug 5104239. Hence, the init() method
      // has now been moved back into the synchronization block. The
      // right approach to solving these problems would be to rewrite
      // this class, based on the latest java.io.ObjectStreamClass.
      desc.init();
    }
    return desc;
  }

  /**
   * The name of the class described by this descriptor.
   */
  public final String getName() {
    return name;
  }

  /**
   * Return the serialVersionUID for this class.
   * The serialVersionUID defines a set of classes all with the same name
   * that have evolved from a common root class and agree to be serialized
   * and deserialized using a common format.
   */
  public static final long getSerialVersionUID(java.lang.Class<?> clazz) {
    ObjectStreamClass theosc = ObjectStreamClass.lookup(clazz);
    if (theosc != null) {
      return theosc.getSerialVersionUID();
    }
    return 0;
  }

  /**
   * Return the serialVersionUID for this class.
   * The serialVersionUID defines a set of classes all with the same name
   * that have evolved from a common root class and agree to be serialized
   * and deserialized using a common format.
   */
  public final long getSerialVersionUID() {
    return suid;
  }

  /**
   * Return the serialVersionUID string for this class.
   * The serialVersionUID defines a set of classes all with the same name
   * that have evolved from a common root class and agree to be serialized
   * and deserialized using a common format.
   */
  public final String getSerialVersionUIDStr() {
    if (suidStr == null) {
      suidStr = Long.toHexString(suid).toUpperCase();
    }
    return suidStr;
  }

  /**
   * Return the actual (computed) serialVersionUID for this class.
   */
  public static final long getActualSerialVersionUID(java.lang.Class<?> clazz) {
    ObjectStreamClass theosc = ObjectStreamClass.lookup(clazz);
    if (theosc != null) {
      return theosc.getActualSerialVersionUID();
    }
    return 0;
  }

  /**
   * Return the actual (computed) serialVersionUID for this class.
   */
  public final long getActualSerialVersionUID() {
    return actualSuid;
  }

  /**
   * Return the actual (computed) serialVersionUID for this class.
   */
  public final String getActualSerialVersionUIDStr() {
    if (actualSuidStr == null) {
      actualSuidStr = Long.toHexString(actualSuid).toUpperCase();
    }
    return actualSuidStr;
  }

  /**
   * Return the class in the local VM that this version is mapped to.
   * Null is returned if there is no corresponding local class.
   */
  public final Class<?> forClass() {
    return ofClass;
  }

  /**
   * Return an array of the fields of this serializable class.
   *
   * @return an array containing an element for each persistent field of this class. Returns an
   * array of length zero if there are no fields.
   * @since JDK1.2
   */
  public ObjectStreamField[] getFields() {
    // Return a copy so the caller can't change the fields.
    if (fields.length > 0) {
      ObjectStreamField[] dup = new ObjectStreamField[fields.length];
      System.arraycopy(fields, 0, dup, 0, fields.length);
      return dup;
    } else {
      return fields;
    }
  }

  public boolean hasField(ValueMember field) {
    try {
      for (int i = 0; i < fields.length; i++) {
        if (fields[i].getName().equals(field.name)) {
          if (fields[i].getSignature().equals(
              ValueUtility.getSignature(field))) {
            return true;
          }
        }
      }
    } catch (Exception exc) {
      // Ignore this; all we want to do is return false
      // Note that ValueUtility.getSignature can throw checked exceptions.
    }

    return false;
  }

  /* Avoid unnecessary allocations. */
  final ObjectStreamField[] getFieldsNoCopy() {
    return fields;
  }

  /**
   * Get the field of this class by name.
   *
   * @return The ObjectStreamField object of the named field or null if there is no such named
   * field.
   */
  public final ObjectStreamField getField(String name) {
        /* Binary search of fields by name.
         */
    for (int i = fields.length - 1; i >= 0; i--) {
      if (name.equals(fields[i].getName())) {
        return fields[i];
      }
    }
    return null;
  }

  public Serializable writeReplace(Serializable value) {
    if (writeReplaceObjectMethod != null) {
      try {
        return (Serializable) writeReplaceObjectMethod.invoke(value, noArgsList);
      } catch (Throwable t) {
        throw new RuntimeException(t);
      }
    } else {
      return value;
    }
  }

  public Object readResolve(Object value) {
    if (readResolveObjectMethod != null) {
      try {
        return readResolveObjectMethod.invoke(value, noArgsList);
      } catch (Throwable t) {
        throw new RuntimeException(t);
      }
    } else {
      return value;
    }
  }

  /**
   * Return a string describing this ObjectStreamClass.
   */
  public final String toString() {
    StringBuffer sb = new StringBuffer();

    sb.append(name);
    sb.append(": static final long serialVersionUID = ");
    sb.append(Long.toString(suid));
    sb.append("L;");
    return sb.toString();
  }

  /*
   * Create a new ObjectStreamClass from a loaded class.
   * Don't call this directly, call lookup instead.
   */
  private ObjectStreamClass(java.lang.Class<?> cl, ObjectStreamClass superdesc,
      boolean serial, boolean extern) {
    ofClass = cl;           /* created from this class */

    if (Proxy.isProxyClass(cl)) {
      forProxyClass = true;
    }

    name = cl.getName();
    isEnum = Enum.class.isAssignableFrom(cl);
    superclass = superdesc;
    serializable = serial;
    if (!forProxyClass) {
      // proxy classes are never externalizable
      externalizable = extern;
    }

        /*
         * Enter this class in the table of known descriptors.
         * Otherwise, when the fields are read it may recurse
         * trying to find the descriptor for itself.
         */
    insertDescriptorFor(this);

        /*
         * The remainder of initialization occurs in init(), which is called
         * after the lock on the global class descriptor table has been
         * released.
         */
  }

  private static final class PersistentFieldsValue
      extends ClassValue<ObjectStreamField[]> {

    PersistentFieldsValue() {
    }

    protected ObjectStreamField[] computeValue(Class<?> type) {
      try {
        Field pf = type.getDeclaredField("serialPersistentFields");
        int mods = pf.getModifiers();
        if (Modifier.isPrivate(mods) && Modifier.isStatic(mods) &&
            Modifier.isFinal(mods)) {
          pf.setAccessible(true);
          java.io.ObjectStreamField[] fields =
              (java.io.ObjectStreamField[]) pf.get(type);
          return translateFields(fields);
        }
      } catch (NoSuchFieldException | IllegalAccessException |
          IllegalArgumentException | ClassCastException e) {
      }
      return null;
    }

    private static ObjectStreamField[] translateFields(
        java.io.ObjectStreamField[] fields) {
      ObjectStreamField[] translation =
          new ObjectStreamField[fields.length];
      for (int i = 0; i < fields.length; i++) {
        translation[i] = new ObjectStreamField(fields[i].getName(),
            fields[i].getType());
      }
      return translation;
    }
  }

  private static final PersistentFieldsValue persistentFieldsValue =
      new PersistentFieldsValue();

    /*
     * Initialize class descriptor.  This method is only invoked on class
     * descriptors created via calls to lookupInternal().  This method is kept
     * separate from the ObjectStreamClass constructor so that lookupInternal
     * does not have to hold onto a global class descriptor table lock while the
     * class descriptor is being initialized (see bug 4165204).
     */


  private void init() {
    synchronized (lock) {

      // See description at definition of initialized.
      if (initialized) {
        return;
      }

      final Class<?> cl = ofClass;

      if (!serializable ||
          externalizable ||
          forProxyClass ||
          name.equals("java.lang.String")) {
        fields = NO_FIELDS;
      } else if (serializable) {
            /* Ask for permission to override field access checks.
             */
        AccessController.doPrivileged(new PrivilegedAction() {
          public Object run() {
                /* Fill in the list of persistent fields.
                 * If it is declared, use the declared serialPersistentFields.
                 * Otherwise, extract the fields from the class itself.
                 */
            fields = persistentFieldsValue.get(cl);

            if (fields == null) {
                    /* Get all of the declared fields for this
                     * Class. setAccessible on all fields so they
                     * can be accessed later.  Create a temporary
                     * ObjectStreamField array to hold each
                     * non-static, non-transient field. Then copy the
                     * temporary array into an array of the correct
                     * size once the number of fields is known.
                     */
              Field[] actualfields = cl.getDeclaredFields();

              int numFields = 0;
              ObjectStreamField[] tempFields =
                  new ObjectStreamField[actualfields.length];
              for (int i = 0; i < actualfields.length; i++) {
                Field fld = actualfields[i];
                int modifiers = fld.getModifiers();
                if (!Modifier.isStatic(modifiers) &&
                    !Modifier.isTransient(modifiers)) {
                  fld.setAccessible(true);
                  tempFields[numFields++] = new ObjectStreamField(fld);
                }
              }

              fields = new ObjectStreamField[numFields];
              System.arraycopy(tempFields, 0, fields, 0, numFields);

            } else {
              // For each declared persistent field, look for an actual
              // reflected Field. If there is one, make sure it's the correct
              // type and cache it in the ObjectStreamClass for that field.
              for (int j = fields.length - 1; j >= 0; j--) {
                try {
                  Field reflField = cl.getDeclaredField(fields[j].getName());
                  if (fields[j].getType() == reflField.getType()) {
                    reflField.setAccessible(true);
                    fields[j].setField(reflField);
                  }
                } catch (NoSuchFieldException e) {
                  // Nothing to do
                }
              }
            }
            return null;
          }
        });

        if (fields.length > 1) {
          Arrays.sort(fields);
        }

            /* Set up field data for use while writing using the API api. */
        computeFieldInfo();
      }

        /* Get the serialVersionUID from the class.
         * It uses the access override mechanism so make sure
         * the field objects is only used here.
         *
         * NonSerializable classes have a serialVerisonUID of 0L.
         */
      if (isNonSerializable() || isEnum) {
        suid = 0L;
      } else {
        // Lookup special Serializable members using reflection.
        AccessController.doPrivileged(new PrivilegedAction() {
          public Object run() {
            if (forProxyClass) {
              // proxy classes always have serialVersionUID of 0L
              suid = 0L;
            } else {
              try {
                final Field f = cl.getDeclaredField("serialVersionUID");
                int mods = f.getModifiers();
                // SerialBug 5:  static final SUID should be read
                if (Modifier.isStatic(mods) && Modifier.isFinal(mods)) {
                  f.setAccessible(true);
                  suid = f.getLong(cl);
                  // SerialBug 2: should be computed after writeObject
                  // actualSuid = computeStructuralUID(cl);
                } else {
                  suid = _computeSerialVersionUID(cl);
                  // SerialBug 2: should be computed after writeObject
                  // actualSuid = computeStructuralUID(cl);
                }
              } catch (NoSuchFieldException ex) {
                suid = _computeSerialVersionUID(cl);
                // SerialBug 2: should be computed after writeObject
                // actualSuid = computeStructuralUID(cl);
              } catch (IllegalAccessException ex) {
                suid = _computeSerialVersionUID(cl);
              }
            }

            writeReplaceObjectMethod = ObjectStreamClass.getInheritableMethod(cl,
                "writeReplace", noTypesList, Object.class);

            readResolveObjectMethod = ObjectStreamClass.getInheritableMethod(cl,
                "readResolve", noTypesList, Object.class);

            if (externalizable) {
              cons = getExternalizableConstructor(cl);
            } else {
              cons = getSerializableConstructor(cl);
            }

            if (serializable && !forProxyClass) {
                    /* Look for the writeObject method
                     * Set the accessible flag on it here. ObjectOutputStream
                     * will call it as necessary.
                     */
              writeObjectMethod = getPrivateMethod(cl, "writeObject",
                  new Class<?>[]{java.io.ObjectOutputStream.class}, Void.TYPE);
              readObjectMethod = getPrivateMethod(cl, "readObject",
                  new Class<?>[]{java.io.ObjectInputStream.class}, Void.TYPE);
            }
            return null;
          }
        });
      }

      // This call depends on a lot of information computed above!
      actualSuid = ObjectStreamClass.computeStructuralUID(this, cl);

      // If we have a write object method, precompute the
      // RMI-IIOP stream format version 2 optional data
      // repository ID.
      if (hasWriteObject()) {
        rmiiiopOptionalDataRepId = computeRMIIIOPOptionalDataRepId();
      }

      // This must be done last.
      initialized = true;
    }
  }

  /**
   * Returns non-static private method with given signature defined by given
   * class, or null if none found.  Access checks are disabled on the
   * returned method (if any).
   */
  private static Method getPrivateMethod(Class<?> cl, String name,
      Class<?>[] argTypes,
      Class<?> returnType) {
    try {
      Method meth = cl.getDeclaredMethod(name, argTypes);
      meth.setAccessible(true);
      int mods = meth.getModifiers();
      return ((meth.getReturnType() == returnType) &&
          ((mods & Modifier.STATIC) == 0) &&
          ((mods & Modifier.PRIVATE) != 0)) ? meth : null;
    } catch (NoSuchMethodException ex) {
      return null;
    }
  }

  // Specific to RMI-IIOP

  /**
   * Java to IDL ptc-02-01-12 1.5.1
   *
   * "The rep_id string passed to the start_value method must be
   * 'RMI:org.omg.custom.class:hashcode:suid' where class is the
   * fully-qualified name of the class whose writeObject method
   * is being invoked and hashcode and suid are the class's hashcode
   * and SUID."
   */
  private String computeRMIIIOPOptionalDataRepId() {

    StringBuffer sbuf = new StringBuffer("RMI:org.omg.custom.");
    sbuf.append(RepositoryId.convertToISOLatin1(this.getName()));
    sbuf.append(':');
    sbuf.append(this.getActualSerialVersionUIDStr());
    sbuf.append(':');
    sbuf.append(this.getSerialVersionUIDStr());

    return sbuf.toString();
  }

  /**
   * This will return null if there is no writeObject method.
   */
  public final String getRMIIIOPOptionalDataRepId() {
    return rmiiiopOptionalDataRepId;
  }

  /*
   * Create an empty ObjectStreamClass for a class about to be read.
   * This is separate from read so ObjectInputStream can assign the
   * wire handle early, before any nested ObjectStreamClass might
   * be read.
   */
  ObjectStreamClass(String n, long s) {
    name = n;
    suid = s;
    superclass = null;
  }


  /*
   * Set the class this version descriptor matches.
   * The base class name and serializable hash must match.
   * Fill in the reflected Fields that will be used
   * for reading.
   */
  final void setClass(Class<?> cl) throws InvalidClassException {

    if (cl == null) {
      localClassDesc = null;
      ofClass = null;
      computeFieldInfo();
      return;
    }

    localClassDesc = lookupInternal(cl);
    if (localClassDesc == null)
    // XXX I18N, logging needed
    {
      throw new InvalidClassException(cl.getName(),
          "Local class not compatible");
    }
    if (suid != localClassDesc.suid) {

            /* Check for exceptional cases that allow mismatched suid. */

            /* Allow adding Serializable or Externalizable
             * to a later release of the class.
             */
      boolean addedSerialOrExtern =
          isNonSerializable() || localClassDesc.isNonSerializable();

            /* Disregard the serialVersionUID of an array
             * when name and cl.Name differ. If resolveClass() returns
             * an array with a different package name,
             * the serialVersionUIDs will not match since the fully
             * qualified array class is used in the
             * computation of the array's serialVersionUID. There is
             * no way to set a permanent serialVersionUID for an array type.
             */

      boolean arraySUID = (cl.isArray() && !cl.getName().equals(name));

      if (!arraySUID && !addedSerialOrExtern) {
        // XXX I18N, logging needed
        throw new InvalidClassException(cl.getName(),
            "Local class not compatible:" +
                " stream classdesc serialVersionUID=" + suid +
                " local class serialVersionUID=" + localClassDesc.suid);
      }
    }

        /* compare the class names, stripping off package names. */
    if (!compareClassNames(name, cl.getName(), '.'))
    // XXX I18N, logging needed
    {
      throw new InvalidClassException(cl.getName(),
          "Incompatible local class name. " +
              "Expected class name compatible with " +
              name);
    }

        /*
         * Test that both implement either serializable or externalizable.
         */

    // The next check is more generic, since it covers the
    // Proxy case, the JDK 1.3 serialization code has
    // both checks
    //if ((serializable && localClassDesc.externalizable) ||
    //    (externalizable && localClassDesc.serializable))
    //    throw new InvalidClassException(localCl.getName(),
    //            "Serializable is incompatible with Externalizable");

    if ((serializable != localClassDesc.serializable) ||
        (externalizable != localClassDesc.externalizable) ||
        (!serializable && !externalizable))

    // XXX I18N, logging needed
    {
      throw new InvalidClassException(cl.getName(),
          "Serialization incompatible with Externalization");
    }

        /* Set up the reflected Fields in the class where the value of each
         * field in this descriptor should be stored.
         * Each field in this ObjectStreamClass (the source) is located (by
         * name) in the ObjectStreamClass of the class(the destination).
         * In the usual (non-versioned case) the field is in both
         * descriptors and the types match, so the reflected Field is copied.
         * If the type does not match, a InvalidClass exception is thrown.
         * If the field is not present in the class, the reflected Field
         * remains null so the field will be read but discarded.
         * If extra fields are present in the class they are ignored. Their
         * values will be set to the default value by the object allocator.
         * Both the src and dest field list are sorted by type and name.
         */

    ObjectStreamField[] destfield =
        (ObjectStreamField[]) localClassDesc.fields;
    ObjectStreamField[] srcfield =
        (ObjectStreamField[]) fields;

    int j = 0;
    nextsrc:
    for (int i = 0; i < srcfield.length; i++) {
            /* Find this field in the dest*/
      for (int k = j; k < destfield.length; k++) {
        if (srcfield[i].getName().equals(destfield[k].getName())) {
                    /* found match */
          if (srcfield[i].isPrimitive() &&
              !srcfield[i].typeEquals(destfield[k])) {
            // XXX I18N, logging needed
            throw new InvalidClassException(cl.getName(),
                "The type of field " +
                    srcfield[i].getName() +
                    " of class " + name +
                    " is incompatible.");
          }

                    /* Skip over any fields in the dest that are not in the src */
          j = k;

          srcfield[i].setField(destfield[j].getField());
          // go on to the next source field
          continue nextsrc;
        }
      }
    }

        /* Set up field data for use while reading from the input stream. */
    computeFieldInfo();

        /* Remember the class this represents */
    ofClass = cl;

        /* get the cache of these methods from the local class
         * implementation.
         */
    readObjectMethod = localClassDesc.readObjectMethod;
    readResolveObjectMethod = localClassDesc.readResolveObjectMethod;
  }

  /* Compare the base class names of streamName and localName.
   *
   * @return  Return true iff the base class name compare.
   * @parameter streamName    Fully qualified class name.
   * @parameter localName     Fully qualified class name.
   * @parameter pkgSeparator  class names use either '.' or '/'.
   *
   * Only compare base class name to allow package renaming.
   */
  static boolean compareClassNames(String streamName,
      String localName,
      char pkgSeparator) {
        /* compare the class names, stripping off package names. */
    int streamNameIndex = streamName.lastIndexOf(pkgSeparator);
    if (streamNameIndex < 0) {
      streamNameIndex = 0;
    }

    int localNameIndex = localName.lastIndexOf(pkgSeparator);
    if (localNameIndex < 0) {
      localNameIndex = 0;
    }

    return streamName.regionMatches(false, streamNameIndex,
        localName, localNameIndex,
        streamName.length() - streamNameIndex);
  }

  /*
   * Compare the types of two class descriptors.
   * They match if they have the same class name and suid
   */
  final boolean typeEquals(ObjectStreamClass other) {
    return (suid == other.suid) &&
        compareClassNames(name, other.name, '.');
  }

  /*
   * Return the superclass descriptor of this descriptor.
   */
  final void setSuperclass(ObjectStreamClass s) {
    superclass = s;
  }

  /*
   * Return the superclass descriptor of this descriptor.
   */
  final ObjectStreamClass getSuperclass() {
    return superclass;
  }

  /**
   * Return whether the class has a readObject method
   */
  final boolean hasReadObject() {
    return readObjectMethod != null;
  }

  /*
   * Return whether the class has a writeObject method
   */
  final boolean hasWriteObject() {
    return writeObjectMethod != null;
  }

  /**
   * Returns when or not this class should be custom
   * marshaled (use chunking).  This should happen if
   * it is Externalizable OR if it or
   * any of its superclasses has a writeObject method,
   */
  final boolean isCustomMarshaled() {
    return (hasWriteObject() || isExternalizable())
        || (superclass != null && superclass.isCustomMarshaled());
  }

  /*
   * Return true if all instances of 'this' Externalizable class
   * are written in block-data mode from the stream that 'this' was read
   * from. <p>
   *
   * In JDK 1.1, all Externalizable instances are not written
   * in block-data mode.
   * In JDK 1.2, all Externalizable instances, by default, are written
   * in block-data mode and the Externalizable instance is terminated with
   * tag TC_ENDBLOCKDATA. Change enabled the ability to skip Externalizable
   * instances.
   *
   * IMPLEMENTATION NOTE:
   *   This should have been a mode maintained per stream; however,
   *   for compatibility reasons, it was only possible to record
   *   this change per class. All Externalizable classes within
   *   a given stream should either have this mode enabled or
   *   disabled. This is enforced by not allowing the PROTOCOL_VERSION
   *   of a stream to he changed after any objects have been written.
   *
   * @see ObjectOutputStream#useProtocolVersion
   * @see ObjectStreamConstants#PROTOCOL_VERSION_1
   * @see ObjectStreamConstants#PROTOCOL_VERSION_2
   *
   * @since JDK 1.2
   */
  boolean hasExternalizableBlockDataMode() {
    return hasExternalizableBlockData;
  }

  /**
   * Creates a new instance of the represented class.  If the class is
   * externalizable, invokes its public no-arg constructor; otherwise, if the
   * class is serializable, invokes the no-arg constructor of the first
   * non-serializable superclass.  Throws UnsupportedOperationException if
   * this class descriptor is not associated with a class, if the associated
   * class is non-serializable or if the appropriate no-arg constructor is
   * inaccessible/unavailable.
   */
  Object newInstance()
      throws InstantiationException, InvocationTargetException,
      UnsupportedOperationException {
    if (cons != null) {
      try {
        return cons.newInstance(new Object[0]);
      } catch (IllegalAccessException ex) {
        // should not occur, as access checks have been suppressed
        InternalError ie = new InternalError();
        ie.initCause(ex);
        throw ie;
      }
    } else {
      throw new UnsupportedOperationException();
    }
  }

  /**
   * Returns public no-arg constructor of given class, or null if none found.
   * Access checks are disabled on the returned constructor (if any), since
   * the defining class may still be non-public.
   */
  private static Constructor getExternalizableConstructor(Class<?> cl) {
    try {
      Constructor cons = cl.getDeclaredConstructor(new Class<?>[0]);
      cons.setAccessible(true);
      return ((cons.getModifiers() & Modifier.PUBLIC) != 0) ?
          cons : null;
    } catch (NoSuchMethodException ex) {
      return null;
    }
  }

  /**
   * Returns subclass-accessible no-arg constructor of first non-serializable
   * superclass, or null if none found.  Access checks are disabled on the
   * returned constructor (if any).
   */
  private static Constructor getSerializableConstructor(Class<?> cl) {
    Class<?> initCl = cl;
    while (Serializable.class.isAssignableFrom(initCl)) {
      if ((initCl = initCl.getSuperclass()) == null) {
        return null;
      }
    }
    try {
      Constructor cons = initCl.getDeclaredConstructor(new Class<?>[0]);
      int mods = cons.getModifiers();
      if ((mods & Modifier.PRIVATE) != 0 ||
          ((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0 &&
              !packageEquals(cl, initCl))) {
        return null;
      }
      cons = bridge.newConstructorForSerialization(cl, cons);
      cons.setAccessible(true);
      return cons;
    } catch (NoSuchMethodException ex) {
      return null;
    }
  }

  /*
   * Return the ObjectStreamClass of the local class this one is based on.
   */
  final ObjectStreamClass localClassDescriptor() {
    return localClassDesc;
  }

  /*
   * Get the Serializability of the class.
   */
  boolean isSerializable() {
    return serializable;
  }

  /*
   * Get the externalizability of the class.
   */
  boolean isExternalizable() {
    return externalizable;
  }

  boolean isNonSerializable() {
    return !(externalizable || serializable);
  }

  /*
   * Calculate the size of the array needed to store primitive data and the
   * number of object references to read when reading from the input
   * stream.
   */
  private void computeFieldInfo() {
    primBytes = 0;
    objFields = 0;

    for (int i = 0; i < fields.length; i++) {
      switch (fields[i].getTypeCode()) {
        case 'B':
        case 'Z':
          primBytes += 1;
          break;
        case 'C':
        case 'S':
          primBytes += 2;
          break;

        case 'I':
        case 'F':
          primBytes += 4;
          break;
        case 'J':
        case 'D':
          primBytes += 8;
          break;

        case 'L':
        case '[':
          objFields += 1;
          break;
      }
    }
  }

  private static void msg(String str) {
    System.out.println(str);
  }

    /* JDK 1.5 has introduced some new modifier bits (such as SYNTHETIC)
     * that can affect the SVUID computation (see bug 4897937).  These bits
     * must be ignored, as otherwise interoperability with ORBs in earlier
     * JDK versions can be compromised.  I am adding these masks for this
     * purpose as discussed in the CCC for this bug (see http://ccc.sfbay/4897937).
     */

  public static final int CLASS_MASK = Modifier.PUBLIC | Modifier.FINAL |
      Modifier.INTERFACE | Modifier.ABSTRACT;
  public static final int FIELD_MASK = Modifier.PUBLIC | Modifier.PRIVATE |
      Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL |
      Modifier.TRANSIENT | Modifier.VOLATILE;
  public static final int METHOD_MASK = Modifier.PUBLIC | Modifier.PRIVATE |
      Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL |
      Modifier.SYNCHRONIZED | Modifier.NATIVE | Modifier.ABSTRACT |
      Modifier.STRICT;

  /*
   * Compute a hash for the specified class.  Incrementally add
   * items to the hash accumulating in the digest stream.
   * Fold the hash into a long.  Use the SHA secure hash function.
   */
  private static long _computeSerialVersionUID(Class<?> cl) {
    if (DEBUG_SVUID) {
      msg("Computing SerialVersionUID for " + cl);
    }
    ByteArrayOutputStream devnull = new ByteArrayOutputStream(512);

    long h = 0;
    try {
      MessageDigest md = MessageDigest.getInstance("SHA");
      DigestOutputStream mdo = new DigestOutputStream(devnull, md);
      DataOutputStream data = new DataOutputStream(mdo);

      if (DEBUG_SVUID) {
        msg("\twriteUTF( \"" + cl.getName() + "\" )");
      }
      data.writeUTF(cl.getName());

      int classaccess = cl.getModifiers();
      classaccess &= (Modifier.PUBLIC | Modifier.FINAL |
          Modifier.INTERFACE | Modifier.ABSTRACT);

            /* Workaround for javac bug that only set ABSTRACT for
             * interfaces if the interface had some methods.
             * The ABSTRACT bit reflects that the number of methods > 0.
             * This is required so correct hashes can be computed
             * for existing class files.
             * Previously this hack was previously present in the VM.
             */
      Method[] method = cl.getDeclaredMethods();
      if ((classaccess & Modifier.INTERFACE) != 0) {
        classaccess &= (~Modifier.ABSTRACT);
        if (method.length > 0) {
          classaccess |= Modifier.ABSTRACT;
        }
      }

      // Mask out any post-1.4 attributes
      classaccess &= CLASS_MASK;

      if (DEBUG_SVUID) {
        msg("\twriteInt( " + classaccess + " ) ");
      }
      data.writeInt(classaccess);

            /*
             * Get the list of interfaces supported,
             * Accumulate their names their names in Lexical order
             * and add them to the hash
             */
      if (!cl.isArray()) {
                /* In 1.2fcs, getInterfaces() was modified to return
                 * {java.lang.Cloneable, java.io.Serializable} when
                 * called on array classes.  These values would upset
                 * the computation of the hash, so we explicitly omit
                 * them from its computation.
                 */

        Class<?> interfaces[] = cl.getInterfaces();
        Arrays.sort(interfaces, compareClassByName);

        for (int i = 0; i < interfaces.length; i++) {
          if (DEBUG_SVUID) {
            msg("\twriteUTF( \"" + interfaces[i].getName() + "\" ) ");
          }
          data.writeUTF(interfaces[i].getName());
        }
      }

            /* Sort the field names to get a deterministic order */
      Field[] field = cl.getDeclaredFields();
      Arrays.sort(field, compareMemberByName);

      for (int i = 0; i < field.length; i++) {
        Field f = field[i];

                /* Include in the hash all fields except those that are
                 * private transient and private static.
                 */
        int m = f.getModifiers();
        if (Modifier.isPrivate(m) &&
            (Modifier.isTransient(m) || Modifier.isStatic(m))) {
          continue;
        }

        if (DEBUG_SVUID) {
          msg("\twriteUTF( \"" + f.getName() + "\" ) ");
        }
        data.writeUTF(f.getName());

        // Mask out any post-1.4 bits
        m &= FIELD_MASK;

        if (DEBUG_SVUID) {
          msg("\twriteInt( " + m + " ) ");
        }
        data.writeInt(m);

        if (DEBUG_SVUID) {
          msg("\twriteUTF( \"" + getSignature(f.getType()) + "\" ) ");
        }
        data.writeUTF(getSignature(f.getType()));
      }

      if (hasStaticInitializer(cl)) {
        if (DEBUG_SVUID) {
          msg("\twriteUTF( \"<clinit>\" ) ");
        }
        data.writeUTF("<clinit>");

        if (DEBUG_SVUID) {
          msg("\twriteInt( " + Modifier.STATIC + " )");
        }
        data.writeInt(Modifier.STATIC); // TBD: what modifiers does it have

        if (DEBUG_SVUID) {
          msg("\twriteUTF( \"()V\" )");
        }
        data.writeUTF("()V");
      }

            /*
             * Get the list of constructors including name and signature
             * Sort lexically, add all except the private constructors
             * to the hash with their access flags
             */

      MethodSignature[] constructors =
          MethodSignature.removePrivateAndSort(cl.getDeclaredConstructors());
      for (int i = 0; i < constructors.length; i++) {
        MethodSignature c = constructors[i];
        String mname = "<init>";
        String desc = c.signature;
        desc = desc.replace('/', '.');
        if (DEBUG_SVUID) {
          msg("\twriteUTF( \"" + mname + "\" )");
        }
        data.writeUTF(mname);

        // mask out post-1.4 modifiers
        int modifier = c.member.getModifiers() & METHOD_MASK;

        if (DEBUG_SVUID) {
          msg("\twriteInt( " + modifier + " ) ");
        }
        data.writeInt(modifier);

        if (DEBUG_SVUID) {
          msg("\twriteUTF( \"" + desc + "\" )");
        }
        data.writeUTF(desc);
      }

            /* Include in the hash all methods except those that are
             * private transient and private static.
             */
      MethodSignature[] methods =
          MethodSignature.removePrivateAndSort(method);
      for (int i = 0; i < methods.length; i++) {
        MethodSignature m = methods[i];
        String desc = m.signature;
        desc = desc.replace('/', '.');

        if (DEBUG_SVUID) {
          msg("\twriteUTF( \"" + m.member.getName() + "\" )");
        }
        data.writeUTF(m.member.getName());

        // mask out post-1.4 modifiers
        int modifier = m.member.getModifiers() & METHOD_MASK;

        if (DEBUG_SVUID) {
          msg("\twriteInt( " + modifier + " ) ");
        }
        data.writeInt(modifier);

        if (DEBUG_SVUID) {
          msg("\twriteUTF( \"" + desc + "\" )");
        }
        data.writeUTF(desc);
      }

            /* Compute the hash value for this class.
             * Use only the first 64 bits of the hash.
             */
      data.flush();
      byte hasharray[] = md.digest();
      for (int i = 0; i < Math.min(8, hasharray.length); i++) {
        h += (long) (hasharray[i] & 255) << (i * 8);
      }
    } catch (IOException ignore) {
            /* can't happen, but be deterministic anyway. */
      h = -1;
    } catch (NoSuchAlgorithmException complain) {
      SecurityException se = new SecurityException();
      se.initCause(complain);
      throw se;
    }

    return h;
  }

  private static long computeStructuralUID(com.sun.corba.se.impl.io.ObjectStreamClass osc,
      Class<?> cl) {
    ByteArrayOutputStream devnull = new ByteArrayOutputStream(512);

    long h = 0;
    try {

      if ((!java.io.Serializable.class.isAssignableFrom(cl)) ||
          (cl.isInterface())) {
        return 0;
      }

      if (java.io.Externalizable.class.isAssignableFrom(cl)) {
        return 1;
      }

      MessageDigest md = MessageDigest.getInstance("SHA");
      DigestOutputStream mdo = new DigestOutputStream(devnull, md);
      DataOutputStream data = new DataOutputStream(mdo);

      // Get SUID of parent
      Class<?> parent = cl.getSuperclass();
      if ((parent != null))
      // SerialBug 1; acc. to spec the one for
      // java.lang.object
      // should be computed and put
      //     && (parent != java.lang.Object.class))
      {
        //data.writeLong(computeSerialVersionUID(null,parent));
        data.writeLong(computeStructuralUID(lookup(parent), parent));
      }

      if (osc.hasWriteObject()) {
        data.writeInt(2);
      } else {
        data.writeInt(1);
      }

      // CORBA formal 00-11-03 10.6.2:  For each field of the
      // class that is mapped to IDL, sorted lexicographically
      // by Java field name, in increasing order...
      ObjectStreamField[] field = osc.getFields();
      if (field.length > 1) {
        Arrays.sort(field, compareObjStrFieldsByName);
      }

      // ...Java field name in UTF encoding, field
      // descriptor, as defined by the JVM spec...
      for (int i = 0; i < field.length; i++) {
        data.writeUTF(field[i].getName());
        data.writeUTF(field[i].getSignature());
      }

            /* Compute the hash value for this class.
             * Use only the first 64 bits of the hash.
             */
      data.flush();
      byte hasharray[] = md.digest();
      // int minimum = Math.min(8, hasharray.length);
      // SerialBug 3: SHA computation is wrong; for loop reversed
      //for (int i = minimum; i > 0; i--)
      for (int i = 0; i < Math.min(8, hasharray.length); i++) {
        h += (long) (hasharray[i] & 255) << (i * 8);
      }
    } catch (IOException ignore) {
            /* can't happen, but be deterministic anyway. */
      h = -1;
    } catch (NoSuchAlgorithmException complain) {
      SecurityException se = new SecurityException();
      se.initCause(complain);
      throw se;
    }
    return h;
  }

  /**
   * Compute the JVM signature for the class.
   */
  static String getSignature(Class<?> clazz) {
    String type = null;
    if (clazz.isArray()) {
      Class<?> cl = clazz;
      int dimensions = 0;
      while (cl.isArray()) {
        dimensions++;
        cl = cl.getComponentType();
      }
      StringBuffer sb = new StringBuffer();
      for (int i = 0; i < dimensions; i++) {
        sb.append("[");
      }
      sb.append(getSignature(cl));
      type = sb.toString();
    } else if (clazz.isPrimitive()) {
      if (clazz == Integer.TYPE) {
        type = "I";
      } else if (clazz == Byte.TYPE) {
        type = "B";
      } else if (clazz == Long.TYPE) {
        type = "J";
      } else if (clazz == Float.TYPE) {
        type = "F";
      } else if (clazz == Double.TYPE) {
        type = "D";
      } else if (clazz == Short.TYPE) {
        type = "S";
      } else if (clazz == Character.TYPE) {
        type = "C";
      } else if (clazz == Boolean.TYPE) {
        type = "Z";
      } else if (clazz == Void.TYPE) {
        type = "V";
      }
    } else {
      type = "L" + clazz.getName().replace('.', '/') + ";";
    }
    return type;
  }

  /*
   * Compute the JVM method descriptor for the method.
   */
  static String getSignature(Method meth) {
    StringBuffer sb = new StringBuffer();

    sb.append("(");

    Class<?>[] params = meth.getParameterTypes(); // avoid clone
    for (int j = 0; j < params.length; j++) {
      sb.append(getSignature(params[j]));
    }
    sb.append(")");
    sb.append(getSignature(meth.getReturnType()));
    return sb.toString();
  }

  /*
   * Compute the JVM constructor descriptor for the constructor.
   */
  static String getSignature(Constructor cons) {
    StringBuffer sb = new StringBuffer();

    sb.append("(");

    Class<?>[] params = cons.getParameterTypes(); // avoid clone
    for (int j = 0; j < params.length; j++) {
      sb.append(getSignature(params[j]));
    }
    sb.append(")V");
    return sb.toString();
  }

  /*
   * Cache of Class -> ClassDescriptor Mappings.
   */
  static private ObjectStreamClassEntry[] descriptorFor = new ObjectStreamClassEntry[61];

  /*
   * findDescriptorFor a Class.  This looks in the cache for a
   * mapping from Class -> ObjectStreamClass mappings.  The hashCode
   * of the Class is used for the lookup since the Class is the key.
   * The entries are extended from java.lang.ref.SoftReference so the
   * gc will be able to free them if needed.
   */
  private static ObjectStreamClass findDescriptorFor(Class<?> cl) {

    int hash = cl.hashCode();
    int index = (hash & 0x7FFFFFFF) % descriptorFor.length;
    ObjectStreamClassEntry e;
    ObjectStreamClassEntry prev;

        /* Free any initial entries whose refs have been cleared */
    while ((e = descriptorFor[index]) != null && e.get() == null) {
      descriptorFor[index] = e.next;
    }

        /* Traverse the chain looking for a descriptor with ofClass == cl.
         * unlink entries that are unresolved.
         */
    prev = e;
    while (e != null) {
      ObjectStreamClass desc = (ObjectStreamClass) (e.get());
      if (desc == null) {
        // This entry has been cleared,  unlink it
        prev.next = e.next;
      } else {
        if (desc.ofClass == cl) {
          return desc;
        }
        prev = e;
      }
      e = e.next;
    }
    return null;
  }

  /*
   * insertDescriptorFor a Class -> ObjectStreamClass mapping.
   */
  private static void insertDescriptorFor(ObjectStreamClass desc) {
    // Make sure not already present
    if (findDescriptorFor(desc.ofClass) != null) {
      return;
    }

    int hash = desc.ofClass.hashCode();
    int index = (hash & 0x7FFFFFFF) % descriptorFor.length;
    ObjectStreamClassEntry e = new ObjectStreamClassEntry(desc);
    e.next = descriptorFor[index];
    descriptorFor[index] = e;
  }

  private static Field[] getDeclaredFields(final Class<?> clz) {
    return (Field[]) AccessController.doPrivileged(new PrivilegedAction() {
      public Object run() {
        return clz.getDeclaredFields();
      }
    });
  }


  /*
   * The name of this descriptor
   */
  private String name;

  /*
   * The descriptor of the supertype.
   */
  private ObjectStreamClass superclass;

  /*
   * Flags for Serializable and Externalizable.
   */
  private boolean serializable;
  private boolean externalizable;

  /*
   * Array of persistent fields of this class, sorted by
   * type and name.
   */
  private ObjectStreamField[] fields;

  /*
   * Class that is a descriptor for in this virtual machine.
   */
  private Class<?> ofClass;

  /*
   * True if descriptor for a proxy class.
   */
  boolean forProxyClass;


  /*
   * SerialVersionUID for this class.
   */
  private long suid = kDefaultUID;
  private String suidStr = null;

  /*
   * Actual (computed) SerialVersionUID for this class.
   */
  private long actualSuid = kDefaultUID;
  private String actualSuidStr = null;

  /*
   * The total number of bytes of primitive fields.
   * The total number of object fields.
   */
  int primBytes;
  int objFields;

  /**
   * Flag indicating whether or not this instance has
   * successfully completed initialization.  This is to
   * try to fix bug 4373844.  Working to move to
   * reusing java.io.ObjectStreamClass for JDK 1.5.
   */
  private boolean initialized = false;

  /* Internal lock object. */
  private Object lock = new Object();

  /* In JDK 1.1, external data was not written in block mode.
   * As of JDK 1.2, external data is written in block data mode. This
   * flag enables JDK 1.2 to be able to read JDK 1.1 written external data.
   *
   * @since JDK 1.2
   */
  private boolean hasExternalizableBlockData;
  Method writeObjectMethod;
  Method readObjectMethod;
  private transient Method writeReplaceObjectMethod;
  private transient Method readResolveObjectMethod;
  private Constructor cons;

  /**
   * Beginning in Java to IDL ptc/02-01-12, RMI-IIOP has a
   * stream format version 2 which puts a fake valuetype around
   * a Serializable's optional custom data.  This valuetype has
   * a special repository ID made from the Serializable's
   * information which we are pre-computing and
   * storing here.
   */
  private String rmiiiopOptionalDataRepId = null;

  /*
   * ObjectStreamClass that this one was built from.
   */
  private ObjectStreamClass localClassDesc;

  /* Find out if the class has a static class initializer <clinit> */
  private static Method hasStaticInitializerMethod = null;

  /**
   * Returns true if the given class defines a static initializer method,
   * false otherwise.
   */
  private static boolean hasStaticInitializer(Class<?> cl) {
    if (hasStaticInitializerMethod == null) {
      Class<?> classWithThisMethod = null;

      try {
        if (classWithThisMethod == null) {
          classWithThisMethod = java.io.ObjectStreamClass.class;
        }

        hasStaticInitializerMethod =
            classWithThisMethod.getDeclaredMethod("hasStaticInitializer",
                new Class<?>[]{Class.class});
      } catch (NoSuchMethodException ex) {
      }

      if (hasStaticInitializerMethod == null) {
        // XXX I18N, logging needed
        throw new InternalError("Can't find hasStaticInitializer method on "
            + classWithThisMethod.getName());
      }
      hasStaticInitializerMethod.setAccessible(true);
    }

    try {
      Boolean retval = (Boolean)
          hasStaticInitializerMethod.invoke(null, new Object[]{cl});
      return retval.booleanValue();
    } catch (Exception ex) {
      // XXX I18N, logging needed
      InternalError ie = new InternalError("Error invoking hasStaticInitializer");
      ie.initCause(ex);
      throw ie;
    }
  }


  /**
   * use serialVersionUID from JDK 1.1. for interoperability
   */
  private static final long serialVersionUID = -6120832682080437368L;

  /**
   * Set serialPersistentFields of a Serializable class to this value to
   * denote that the class has no Serializable fields.
   */
  public static final ObjectStreamField[] NO_FIELDS =
      new ObjectStreamField[0];

  /*
   * Entries held in the Cache of known ObjectStreamClass objects.
   * Entries are chained together with the same hash value (modulo array size).
   */
  private static class ObjectStreamClassEntry // extends java.lang.ref.SoftReference
  {

    ObjectStreamClassEntry(ObjectStreamClass c) {
      //super(c);
      this.c = c;
    }

    ObjectStreamClassEntry next;

    public Object get() {
      return c;
    }

    private ObjectStreamClass c;
  }

  /*
   * Comparator object for Classes and Interfaces
   */
  private static Comparator compareClassByName =
      new CompareClassByName();

  private static class CompareClassByName implements Comparator {

    public int compare(Object o1, Object o2) {
      Class<?> c1 = (Class) o1;
      Class<?> c2 = (Class) o2;
      return (c1.getName()).compareTo(c2.getName());
    }
  }

  /**
   * Comparator for ObjectStreamFields by name
   */
  private final static Comparator compareObjStrFieldsByName
      = new CompareObjStrFieldsByName();

  private static class CompareObjStrFieldsByName implements Comparator {

    public int compare(Object o1, Object o2) {
      ObjectStreamField osf1 = (ObjectStreamField) o1;
      ObjectStreamField osf2 = (ObjectStreamField) o2;

      return osf1.getName().compareTo(osf2.getName());
    }
  }

  /*
   * Comparator object for Members, Fields, and Methods
   */
  private static Comparator compareMemberByName =
      new CompareMemberByName();

  private static class CompareMemberByName implements Comparator {

    public int compare(Object o1, Object o2) {
      String s1 = ((Member) o1).getName();
      String s2 = ((Member) o2).getName();

      if (o1 instanceof Method) {
        s1 += getSignature((Method) o1);
        s2 += getSignature((Method) o2);
      } else if (o1 instanceof Constructor) {
        s1 += getSignature((Constructor) o1);
        s2 += getSignature((Constructor) o2);
      }
      return s1.compareTo(s2);
    }
  }

  /* It is expensive to recompute a method or constructor signature
     many times, so compute it only once using this data structure. */
  private static class MethodSignature implements Comparator {

    Member member;
    String signature;      // cached parameter signature

    /* Given an array of Method or Constructor members,
       return a sorted array of the non-private members.*/
        /* A better implementation would be to implement the returned data
           structure as an insertion sorted link list.*/
    static MethodSignature[] removePrivateAndSort(Member[] m) {
      int numNonPrivate = 0;
      for (int i = 0; i < m.length; i++) {
        if (!Modifier.isPrivate(m[i].getModifiers())) {
          numNonPrivate++;
        }
      }
      MethodSignature[] cm = new MethodSignature[numNonPrivate];
      int cmi = 0;
      for (int i = 0; i < m.length; i++) {
        if (!Modifier.isPrivate(m[i].getModifiers())) {
          cm[cmi] = new MethodSignature(m[i]);
          cmi++;
        }
      }
      if (cmi > 0) {
        Arrays.sort(cm, cm[0]);
      }
      return cm;
    }

    /* Assumes that o1 and o2 are either both methods
       or both constructors.*/
    public int compare(Object o1, Object o2) {
            /* Arrays.sort calls compare when o1 and o2 are equal.*/
      if (o1 == o2) {
        return 0;
      }

      MethodSignature c1 = (MethodSignature) o1;
      MethodSignature c2 = (MethodSignature) o2;

      int result;
      if (isConstructor()) {
        result = c1.signature.compareTo(c2.signature);
      } else { // is a Method.
        result = c1.member.getName().compareTo(c2.member.getName());
        if (result == 0) {
          result = c1.signature.compareTo(c2.signature);
        }
      }
      return result;
    }

    final private boolean isConstructor() {
      return member instanceof Constructor;
    }

    private MethodSignature(Member m) {
      member = m;
      if (isConstructor()) {
        signature = ObjectStreamClass.getSignature((Constructor) m);
      } else {
        signature = ObjectStreamClass.getSignature((Method) m);
      }
    }
  }

  /**
   * Returns non-static, non-abstract method with given signature provided it
   * is defined by or accessible (via inheritance) by the given class, or
   * null if no match found.  Access checks are disabled on the returned
   * method (if any).
   *
   * Copied from the Merlin java.io.ObjectStreamClass.
   */
  private static Method getInheritableMethod(Class<?> cl, String name,
      Class<?>[] argTypes,
      Class<?> returnType) {
    Method meth = null;
    Class<?> defCl = cl;
    while (defCl != null) {
      try {
        meth = defCl.getDeclaredMethod(name, argTypes);
        break;
      } catch (NoSuchMethodException ex) {
        defCl = defCl.getSuperclass();
      }
    }

    if ((meth == null) || (meth.getReturnType() != returnType)) {
      return null;
    }
    meth.setAccessible(true);
    int mods = meth.getModifiers();
    if ((mods & (Modifier.STATIC | Modifier.ABSTRACT)) != 0) {
      return null;
    } else if ((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) {
      return meth;
    } else if ((mods & Modifier.PRIVATE) != 0) {
      return (cl == defCl) ? meth : null;
    } else {
      return packageEquals(cl, defCl) ? meth : null;
    }
  }

  /**
   * Returns true if classes are defined in the same package, false
   * otherwise.
   *
   * Copied from the Merlin java.io.ObjectStreamClass.
   */
  private static boolean packageEquals(Class<?> cl1, Class<?> cl2) {
    Package pkg1 = cl1.getPackage(), pkg2 = cl2.getPackage();
    return ((pkg1 == pkg2) || ((pkg1 != null) && (pkg1.equals(pkg2))));
  }
}
